/*
 Created by lustre on 2021/12/5.
在引入此文件之前定义 ESP8266_ENV 或者 ESP32_ENV，
用来导入 WiFi  WiFiClient等类
*/

#ifdef ESP8266
#include <ESP8266WiFi.h>
#elif defined ESP32

#include <WiFi.h>
#include <MD5Builder.h>

#endif

#ifndef D1EMPTY_LIGHT_H
#define D1EMPTY_LIGHT_H

#define CMD_ID "id"
#define OP_CODE "op"
#define MSG_OK 0
#define MSG_FAILED (-1)

typedef struct {
    int cmdIdVal;
    int opCode;
} MsgHeader;

typedef struct {
    // wifi 的ssid和密码
    char* ssid;
    char* passphrase;
    // 服务端地址与端口
    char* host;
    uint16_t serverPost;
    // 设备用户名密码
    char* userName;
    char* passwd;
    // 设备属性数，和属性
    uint16_t attrCount;
    char* devAttr;
} ConnectInfo;

class LightClient {
private:
    WiFiClient client;

public:
    int readInt(char terminator) {
        String str = client.readStringUntil(terminator);
        return str.toInt();
    }

    String readKey() {
        String key = client.readStringUntil(':');
        key.trim();
        return key;
    }

    // 发现不是key 丢掉后续
    void dropLine(String key) {
        Serial.print("drop msg:\n\t");
        Serial.print(key);
        Serial.print(':');
        Serial.print(client.readStringUntil('\n'));
    }

    // 读字符串
    String readStringUntil(char terminator) {
        return client.readStringUntil(terminator);
    }

    int readMsgHeader(MsgHeader* msgHeader) {
        String cmdIdKey = readKey();
        if (cmdIdKey.isEmpty()) {
            return MSG_FAILED;
        }
        if (!cmdIdKey.equals(CMD_ID)) {
            dropLine(cmdIdKey);
            return MSG_FAILED;
        }
        int cmdIdVal = readInt('\n');

        String opCodeKey = readKey();
        if (!opCodeKey.equals(OP_CODE)) {
            dropLine(opCodeKey);
            return MSG_FAILED;
        }
        int opCode = readInt('\n');

        msgHeader->cmdIdVal = cmdIdVal;
        msgHeader->opCode = opCode;
        return MSG_OK;
    }

    static void connectWifi(const char* ssid, const char* passphrase) {
        WiFiClass::mode(WIFI_STA);
        WiFi.begin(ssid, passphrase);

        Serial.println("connecting to router... ");
        //等待wifi连接成功
        while (WiFiClass::status() != WL_CONNECTED) {
            Serial.print(".");
            delay(500);
        }
        Serial.print("\nWiFi connected, local IP address:");
        Serial.println(WiFi.localIP());
    }

    bool login(const char* host,
               const uint16_t serverPort,
               const char* userName,
               const char* passwd) {
        Serial.printf("connecting to %s\n", host);
        WiFiClient wiFiClient;
        if (WiFiClass::status() != WL_CONNECTED) {
            return false;
        }
        while (!wiFiClient.connect(host, serverPort)) {
            Serial.println("connection failed,wait 1 sec...");
            delay(1000);
        }
        Serial.println("connect tcp channel success");
        this->client = wiFiClient;
        // 获取nonce
        uint8_t nonce[8];
        wiFiClient.write("nonce");
        wiFiClient.flush();
        wiFiClient.readBytes(nonce, 8);
        // 发送用户名
        wiFiClient.write(strlen_P(userName));
        wiFiClient.write(userName);
        wiFiClient.flush();
        // md5(userName + passwd)
        uint8_t md[16];
        MD5Builder md5Builder{};
        md5Builder.begin();
        md5Builder.add(userName);
        md5Builder.add(passwd);
        md5Builder.calculate();
        md5Builder.getBytes(md);
        // 认证的摘要 md5(nonce + md5(userName + passwd))
        md5Builder.begin();
        md5Builder.add(nonce, 8);
        md5Builder.add(md, 16);
        md5Builder.calculate();
        md5Builder.getBytes(md);
        wiFiClient.write(md, 16);
        wiFiClient.flush();
        delay(200);
        return wiFiClient.connected();
    }

    void helloServer(const uint16_t attrCount, const char* devAttr) {
        //与服务端握手
        client.println("hello");
        uint8_t mac[6];
        WiFi.macAddress(mac);
        Serial.printf("MAC is:%02X%02X%02X%02X%02X%02X\n", mac[0], mac[1],
                      mac[2], mac[3], mac[4], mac[5]);
        // sn 长度，这里将mac作为设备序列号。长度为6字节
        client.write(6);
        client.write(mac, 6);
        client.write('\n');
        //属性行数，占两个字节
        client.write((0xff00 & attrCount) >> 8);
        client.write(0xff & attrCount);
        client.write('\n');
        client.write(devAttr);
        client.write('\n');
        client.flush();
    }

    void connectServer(ConnectInfo* connectInfo) {
        connectWifi(connectInfo->ssid, connectInfo->passphrase);

        if (!login(connectInfo->host, connectInfo->serverPost,
                   connectInfo->userName, connectInfo->passwd)) {
            Serial.println("login in failed!");
            return;
        }
        helloServer(connectInfo->attrCount, connectInfo->devAttr);
    }
};
#endif  // D1EMPTY_LIGHT_H
